<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div class="cl-preview-section"><h1 id="【图解】flutter测试的那些事">【图解】Flutter测试的那些事</h1>
</div><div class="cl-preview-section"><p><img src="https://o.devio.org/images/fa/res/flutter-test.gif" alt="flutter-test"></p>
</div><div class="cl-preview-section"><p>软件测试是发现程序错误衡量软件质量必不少的一个环节，在企业中会有专门的软件测试工程师来负责软件测试和质量的障。作为一名Flutter开发人员了解Flutter测试的方法和手段有助于减少程序的Bug开发出更高质量的应用。那么Flutter是如何进行测试的呢？</p>
</div><div class="cl-preview-section"><p>在本篇教程中将下大家分享Flutter的主流测试方式以和案例，在Flutter中主要有以下三种类型的测试：</p>
</div><div class="cl-preview-section"><ul>
<li>单元测试</li>
<li>Widget测试</li>
<li>集成测试</li>
</ul>
</div><div class="cl-preview-section"><h2 id="单元测试">单元测试</h2>
</div><div class="cl-preview-section"><p>测试单一功能、方法或类，单元测试通常不会读取/写入磁盘、渲染到屏幕，也不会从运行测试的进程外部接收用户操作。单元测试的目标是在各种条件下验证逻辑单元的正确性。</p>
</div><div class="cl-preview-section"><blockquote>
<p>如果所测试对象有外部依赖，那么外部依赖要能够被模拟出来，否则是无法进行单元测试。</p>
</blockquote>
</div><div class="cl-preview-section"><h3 id="所需依赖">所需依赖</h3>
</div><div class="cl-preview-section"><pre><code>dev_dependencies:
  flutter_test:
    sdk: flutter
</code></pre>
</div><div class="cl-preview-section"><h3 id="案例">案例</h3>
</div><div class="cl-preview-section"><pre class=" language-dart"><code class="prism  language-dart"><span class="token comment">///单元测试</span>
<span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">///测试HiCache的存储和读取</span>
  <span class="token function">test</span><span class="token punctuation">(</span><span class="token string">'测试HiCache'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">async</span> <span class="token punctuation">{</span>
    <span class="token comment">//fix ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.</span>
    TestWidgetsFlutterBinding<span class="token punctuation">.</span><span class="token function">ensureInitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//fix MissingPluginException(No implementation found for method getAll on channel plugins.flutter.io/shared_preferences)</span>
    SharedPreferences<span class="token punctuation">.</span><span class="token function">setMockInitialValues</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> HiCache<span class="token punctuation">.</span><span class="token function">preInit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> key <span class="token operator">=</span> <span class="token string">"testHiCache"</span><span class="token punctuation">,</span> value <span class="token operator">=</span> <span class="token string">"Hello."</span><span class="token punctuation">;</span>
    HiCache<span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setString</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>HiCache<span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p>在这个案例中我们对项目中的缓存模块<code>HiCache</code>进行了单元测试，主要用来测试它的存储和读取功能是否正常。</p>
</div><div class="cl-preview-section"><h2 id="widget测试">Widget测试</h2>
</div><div class="cl-preview-section"><p>Widget 测试可以用于测试单独的 class, function, 和 Widget。</p>
</div><div class="cl-preview-section"><blockquote>
<p>Widget测试具有一定的局限性，所测试的Widget必须要能够独立运行，或者所以依赖条件能够被模拟出来。</p>
</blockquote>
</div><div class="cl-preview-section"><h3 id="所需依赖-1">所需依赖</h3>
</div><div class="cl-preview-section"><pre><code>dev_dependencies:
  flutter_test:
    sdk: flutter
</code></pre>
</div><div class="cl-preview-section"><p>按照上述要求整个APP有哪些Widget能进行Widget测试？</p>
</div><div class="cl-preview-section"><h3 id="案例-1">案例</h3>
</div><div class="cl-preview-section"><pre class=" language-dart"><code class="prism  language-dart"><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">class</span> <span class="token class-name">UnKnownPage</span> <span class="token keyword">extends</span> <span class="token class-name">StatefulWidget</span> <span class="token punctuation">{</span>
  <span class="token metadata symbol">@override</span>
  _UnKnownPageState <span class="token function">createState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token function">_UnKnownPageState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name">_UnKnownPageState</span> <span class="token keyword">extends</span> <span class="token class-name">State</span><span class="token operator">&lt;</span>UnKnownPage<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  <span class="token metadata symbol">@override</span>
  Widget <span class="token function">build</span><span class="token punctuation">(</span>BuildContext context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token function">Scaffold</span><span class="token punctuation">(</span>
      appBar<span class="token punctuation">:</span> <span class="token function">AppBar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      body<span class="token punctuation">:</span> <span class="token function">Container</span><span class="token punctuation">(</span>
        child<span class="token punctuation">:</span> <span class="token function">Text</span><span class="token punctuation">(</span><span class="token string">'404'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token comment">///Widget测试</span>
<span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token function">testWidgets</span><span class="token punctuation">(</span><span class="token string">'测试UnKnownPage'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>WidgetTester tester<span class="token punctuation">)</span> <span class="token keyword">async</span> <span class="token punctuation">{</span>
    <span class="token comment">//UnKnownPage虽然没有Flutter框架之外的依赖，但因为用到了Scaffold所以需要用MaterialApp包裹</span>
    <span class="token keyword">await</span> tester<span class="token punctuation">.</span><span class="token function">pumpWidget</span><span class="token punctuation">(</span><span class="token function">MaterialApp</span><span class="token punctuation">(</span>home<span class="token punctuation">:</span> <span class="token function">UnKnownPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>find<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token string">'404'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> findsOneWidget<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p>在这个案例中我们对项目中的<code>UnKnownPage</code>Widget进行了测试，主要用来测试该页面中是否存在一个内容为404的Text。</p>
</div><div class="cl-preview-section"><h2 id="集成测试">集成测试</h2>
</div><div class="cl-preview-section"><p>集成测试主要是测试各部分一起运行或者测试一个应用在真实设备上运行的表现的时候就要用到集成测试。</p>
</div><div class="cl-preview-section"><h3 id="所需依赖-2">所需依赖</h3>
</div><div class="cl-preview-section"><pre><code>dev_dependencies:
  flutter_test:
    sdk: flutter
  integration_test:
    sdk: flutter
</code></pre>
</div><div class="cl-preview-section"><h3 id="主要步骤">主要步骤</h3>
</div><div class="cl-preview-section"><ol>
<li>添加测试驱动</li>
<li>编写测试用例</li>
<li>运行测试用例</li>
<li>查看结果</li>
</ol>
</div><div class="cl-preview-section"><h4 id="添加测试驱动">添加测试驱动</h4>
</div><div class="cl-preview-section"><p>添加测试驱动的目的是为了方便通过flutter drive命令运行集成测试：</p>
</div><div class="cl-preview-section"><p>在项目根目录创建<code>test_driver</code>目录并添加文件<code>integration_test.dart</code>：</p>
</div><div class="cl-preview-section"><pre class=" language-dart"><code class="prism  language-dart"><span class="token keyword">import</span> <span class="token string">'package:integration_test/integration_test_driver.dart'</span><span class="token punctuation">;</span>
Future<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token function">integrationDriver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h4 id="编写测试用例">编写测试用例</h4>
</div><div class="cl-preview-section"><p>在项目根目录创建<code>integration_test</code>目录并添加文件<code>app_test.dart</code>。接下来我们就来测试下登录模块的跳转功能：</p>
</div><div class="cl-preview-section"><pre class=" language-dart"><code class="prism  language-dart"><span class="token keyword">import</span> <span class="token string">'package:flutter/material.dart'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token string">'package:flutter_bili_app/main.dart'</span> <span class="token operator">as</span> app<span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token string">'package:flutter_bili_app/navigator/hi_navigator.dart'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token string">'package:flutter_test/flutter_test.dart'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token string">'package:integration_test/integration_test.dart'</span><span class="token punctuation">;</span>

<span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  IntegrationTestWidgetsFlutterBinding<span class="token punctuation">.</span><span class="token function">ensureInitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">testWidgets</span><span class="token punctuation">(</span><span class="token string">'Test login jump'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>WidgetTester tester<span class="token punctuation">)</span> <span class="token keyword">async</span> <span class="token punctuation">{</span>
    <span class="token comment">//构建应用</span>
    app<span class="token punctuation">.</span><span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//捕获一帧</span>
    <span class="token keyword">await</span> tester<span class="token punctuation">.</span><span class="token function">pumpAndSettle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//通过key来查找注册按钮</span>
    <span class="token keyword">var</span> registrationBtn <span class="token operator">=</span> find<span class="token punctuation">.</span><span class="token function">byKey</span><span class="token punctuation">(</span><span class="token function">Key</span><span class="token punctuation">(</span><span class="token string">'registration'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//触发按钮的点击事件</span>
    <span class="token keyword">await</span> tester<span class="token punctuation">.</span><span class="token function">tap</span><span class="token punctuation">(</span>registrationBtn<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//捕获一帧</span>
    <span class="token keyword">await</span> tester<span class="token punctuation">.</span><span class="token function">pumpAndSettle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> Future<span class="token punctuation">.</span><span class="token function">delayed</span><span class="token punctuation">(</span><span class="token function">Duration</span><span class="token punctuation">(</span>seconds<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//判断是否跳转到了注册页</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>HiNavigator<span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getCurrent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>routeStatus<span class="token punctuation">,</span>
        RouteStatus<span class="token punctuation">.</span>registration<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//获取返回按钮，并触发返回上一页</span>
    <span class="token keyword">var</span> backBtn <span class="token operator">=</span> find<span class="token punctuation">.</span><span class="token function">byType</span><span class="token punctuation">(</span>BackButton<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> tester<span class="token punctuation">.</span><span class="token function">tap</span><span class="token punctuation">(</span>backBtn<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> tester<span class="token punctuation">.</span><span class="token function">pumpAndSettle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> Future<span class="token punctuation">.</span><span class="token function">delayed</span><span class="token punctuation">(</span><span class="token function">Duration</span><span class="token punctuation">(</span>seconds<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//判断是返回到登录页</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>
        HiNavigator<span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getCurrent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>routeStatus<span class="token punctuation">,</span> RouteStatus<span class="token punctuation">.</span>login<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p>在这个案例中我们通过获取当前页APP页面上的帧，然后基于捕获的这一帧查找到对应控件，并模拟了点击。为了能够判断登录模块的跳转逻辑是否正常在上述代码中我们通过<code>HiNavigator</code>获取到当前的页面路由状态来进行判断。</p>
</div><div class="cl-preview-section"><h4 id="运行测试用例">运行测试用例</h4>
</div><div class="cl-preview-section"><p>运行集成测试的测试用例可以通过以下命令来完成：</p>
</div><div class="cl-preview-section"><pre class=" language-dart"><code class="prism  language-dart">flutter drive   <span class="token operator">--</span>driver<span class="token operator">=</span>test_driver<span class="token operator">/</span>integration_test<span class="token punctuation">.</span>dart   <span class="token operator">--</span>target<span class="token operator">=</span>integration_test<span class="token operator">/</span>app_test<span class="token punctuation">.</span>dart
</code></pre>
</div><div class="cl-preview-section"><ul>
<li>–driver：用于指定测试驱动的路径；</li>
<li>–targe：用于指定测试用例的路径；</li>
</ul>
</div><div class="cl-preview-section"><p>下面实现运行效果图：</p>
</div><div class="cl-preview-section"><p><img src="https://o.devio.org/images/fa/res/IntegrationTest.gif" alt="IntegrationTest"></p>
</div><div class="cl-preview-section"><blockquote>
<p>如果APP之前已经登录过那么需要将其先卸载来清除登录缓存，以便APP能够在测试的时候正常进入到登录页。</p>
</blockquote>
</div><div class="cl-preview-section"><h2 id="最后">最后</h2>
</div><div class="cl-preview-section"><ul>
<li>Integration testing：<a href="https://flutter.dev/docs/testing/integration-tests">https://flutter.dev/docs/testing/integration-tests</a></li>
<li>Flutter 集成测试example：<a href="https://github.com/flutter/plugins/tree/master/packages/integration_test/example">https://github.com/flutter/plugins/tree/master/packages/integration_test/example</a></li>
</ul>
</div> <h1 style="color: red">认准一手，获取完整资料<br>QQ3195303913<br>微信wxywd8<br><br><br><br>高价回收正版课</h1></body></html>